package org.loong.crypto.core.utils;

import java.security.KeyFactory;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Signature;
import java.util.Objects;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;

import org.apache.commons.lang3.StringUtils;
import org.loong.crypto.common.exception.CryptoException;
import org.loong.crypto.core.GlobalBouncyCastleProvider;

public final class SecureUtils {

    /**
     * Create {@link Cipher}.
     *
     * @param algorithm the algorithm
     * @return {@link Cipher}
     */
    public static Cipher createCipher(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        Cipher cipher;
        try {
            cipher = Objects.isNull(provider) ? Cipher.getInstance(algorithm) : Cipher.getInstance(algorithm, provider);
        } catch (Exception e) {
            throw new CryptoException(e);
        }

        return cipher;
    }

    /**
     * Create {@link MessageDigest}.
     *
     * @param algorithm the algorithm
     * @return {@link MessageDigest}
     */
    public static MessageDigest createMessageDigest(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        MessageDigest messageDigest;
        try {
            messageDigest = Objects.isNull(provider) ? MessageDigest.getInstance(algorithm) : MessageDigest.getInstance(algorithm, provider);
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException(e);
        }

        return messageDigest;
    }

    /**
     * Create {@link Mac}.
     *
     * @param algorithm the algorithm
     * @return {@link Mac}
     */
    public static Mac createMac(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        Mac mac;
        try {
            mac = Objects.isNull(provider) ? Mac.getInstance(algorithm) : Mac.getInstance(algorithm, provider);
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException(e);
        }

        return mac;
    }

    /**
     * Create {@link Signature}.
     *
     * @param algorithm the algorithm
     * @return {@link Signature}
     */
    public static Signature createSignature(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        Signature signature;
        try {
            signature = Objects.isNull(provider) ? Signature.getInstance(algorithm) : Signature.getInstance(algorithm, provider);
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException(e);
        }

        return signature;
    }

    /**
     * Create {@link KeyPairGenerator}.
     *
     * @param algorithm the algorithm
     * @return {@link KeyPairGenerator}
     */
    public static KeyPairGenerator createKeyPairGenerator(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        KeyPairGenerator keyPairGen;
        try {
            keyPairGen = Objects.isNull(provider) //
                    ? KeyPairGenerator.getInstance(getMainAlgorithm(algorithm)) //
                    : KeyPairGenerator.getInstance(getMainAlgorithm(algorithm), provider); //
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException(e);
        }
        return keyPairGen;
    }

    /**
     * Create {@link KeyFactory}.
     *
     * @param algorithm the algorithm
     * @return {@link KeyFactory}
     */
    public static KeyFactory createKeyFactory(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        KeyFactory keyFactory;
        try {
            keyFactory = Objects.isNull(provider) //
                    ? KeyFactory.getInstance(getMainAlgorithm(algorithm)) //
                    : KeyFactory.getInstance(getMainAlgorithm(algorithm), provider);
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException(e);
        }
        return keyFactory;
    }

    /**
     * Create {@link SecretKeyFactory}.
     *
     * @param algorithm the algorithm
     * @return {@link KeyFactory}
     */
    public static SecretKeyFactory createSecretKeyFactory(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        SecretKeyFactory keyFactory;
        try {
            keyFactory = Objects.isNull(provider) //
                    ? SecretKeyFactory.getInstance(getMainAlgorithm(algorithm)) //
                    : SecretKeyFactory.getInstance(getMainAlgorithm(algorithm), provider);
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException(e);
        }
        return keyFactory;
    }

    /**
     * Create {@link KeyGenerator}.
     *
     * @param algorithm the algorithm
     * @return {@link KeyGenerator}
     */
    public static KeyGenerator createKeyGenerator(String algorithm) {
        final Provider provider = GlobalBouncyCastleProvider.INSTANCE.getProvider();

        KeyGenerator generator;
        try {
            generator = Objects.isNull(provider) //
                    ? KeyGenerator.getInstance(getMainAlgorithm(algorithm)) //
                    : KeyGenerator.getInstance(getMainAlgorithm(algorithm), provider);
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException(e);
        }
        return generator;
    }

    /**
     * Principal algorithm name, such as RSA/ECB/PKCS1Padding is RSA.
     *
     * @param algorithm the algorithm, format is XXXwithXXX
     * @return algorithm name
     */
    public static String getMainAlgorithm(String algorithm) {
        if (StringUtils.isBlank(algorithm)) {
            throw new CryptoException("Algorithm must be not blank!");
        }

        final int slashIndex = algorithm.indexOf("/");
        if (slashIndex > 0) {
            return algorithm.substring(0, slashIndex);
        }
        return algorithm;
    }

    /**
     * Obtain the algorithm for key generation, get the second half of the XXXwithXXX algorithm, if ECDSA or SM2, the return algorithm is EC.
     *
     * @param algorithm the algorithm, format is XXXwithXXX
     * @return algorithm name
     */
    public static String getAlgorithmAfterWith(String algorithm) {
        if (StringUtils.startsWithIgnoreCase(algorithm, "ECIESWith")) {
            return "EC";
        }

        int indexOfWith = StringUtils.lastIndexOfIgnoreCase(algorithm, "with");
        if (indexOfWith > 0) {
            algorithm = StringUtils.substringAfter(algorithm, indexOfWith + "with".length());
        }

        if ("ECDSA".equalsIgnoreCase(algorithm) || "SM2".equalsIgnoreCase(algorithm) || "ECIES".equalsIgnoreCase(algorithm)) {
            algorithm = "EC";
        }
        return algorithm;
    }
}
